#pragma once

#include "Log.h"
#include "Task.h"
#include "ThreadPool.hpp"
#include <string>
#include <iostream>
#include <cstdlib>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>

namespace server {
    using namespace std;

    enum {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    class TcpServer; // 声明
    // 线程上下文
    struct ThreadData {
        TcpServer* _tcp_server;
        int _socket; // 用于与客户端进行通信的套接字

        ThreadData(TcpServer* tcp_server, const int& socket)
            : _tcp_server(tcp_server)
            , _socket(socket)
        {}
    };

    // Tcp通信服务端
    class TcpServer {
    private:
        int _listen_sockfd; // 用于监听的套接字，是用来获取链接的，而不是用来通信的
        uint16_t _port;

    public:
        TcpServer(const uint16_t& port = 18989)
            : _listen_sockfd(-1)
            , _port(port)
        {}

        ~TcpServer() {
            close(_listen_sockfd); // 关闭监听套接字，避免套接字泄漏(内存泄漏)
        }

    public:
        void initServer() {
            // 创建监听套接字
            _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_listen_sockfd < 0) { // 创建套接字失败
                logMessage(FATAL, "create listen_socket err!");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create listen_socket success: %d", _listen_sockfd);

            // 服务端绑定服务器的特定端口
            struct sockaddr_in host;
            bzero(&host, sizeof(host));
            host.sin_family = AF_INET;
            host.sin_port = htons(_port);
            host.sin_addr.s_addr = htonl(INADDR_ANY); // 不绑定特定ip
            if (bind(_listen_sockfd, (struct sockaddr*)&host, sizeof(host)) < 0) {
                logMessage(FATAL, "bind listen_socket err!");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind listen_socket success!");

            // 设置监听状态，等待客户端连接
            if (listen(_listen_sockfd, 5/*之后再来解释这个参数*/) < 0) {
                logMessage(FATAL, "listen state set fail!");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen state set success!");
        }

        void start() {
            // signal(SIGCHLD, SIG_IGN);
            ThreadPool<Task>::getSingleton(8)->boost(); // 线程池初始化

            for (;;) {
                // 接收客户端的链接请求，并创建一个与之通信的套接字
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                int sockfd = accept(_listen_sockfd, (struct sockaddr*)&client, &len);
                if (sockfd < 0 ) {
                    // 如果与客户端建立连接失败了，不需要退出
                    // 连接建立失败是常事，如果但凡有一个连接失败服务端就退出的话
                    // 那么其他客户也都别用了
                    // 继续接收下一个连接请求就行
                    logMessage(ERROR, "accept link request err!");
                    continue;
                }
                logMessage(NORMAL, "accept a new link request success, the new sockfd is: %d", sockfd);
                
                //task 与客户端之间进行信息交流(业务处理)
                // //* ver1 单进程单线程
                // ioService(sockfd);

                // //* ver2.1 多进程(用孙子进程解决进程等待问题)
                // pid_t pid = fork();
                // if (pid == 0) { // 子进程
                //     close(_listen_sockfd); // 对于子进程来说监听socket没有用，直接关掉就行。因为写时拷贝，所以子进程关掉不会影响父进程
                //     if (fork() > 0) exit(EXIT_SUCCESS); // 子进程创建出孙子进程后直接退出

                //     // 让孙子进程来完成子进程该做的事情
                //     ioService(sockfd);

                //     // 孙子进程完成任务后直接退出，因为子进程已经终止，所以孙子进程为孤儿进程
                //     // 孙子进程在终止时会由OS来回收资源
                //     exit(EXIT_SUCCESS);
                // }

                // // 父进程
                // if (waitpid(pid, nullptr, 0) > 0) {
                //     // 因为是孙子进程完成任务，子进程已经退出
                //     // 所以父进程很快就能阻塞时等待到子进程退出
                //     // 这样的话父进程就不会阻塞太久，不会表现出串行
                //     // 如果你要问为什么不非阻塞时等待，那么我告诉你
                //     // 首先非阻塞时等待会大量消耗CPU资源
                //     // 然后因为父进程它在监听时是阻塞的，在阻塞状态下是不能轮询的
                //     // 假如父进程大多数时间都在监听，那么这样能等谁啊

                //     logMessage(NORMAL, "wait child process success pid: %d", pid);
                // }

                // //* ver2.2 多进程(通过忽略信号SIGCHLD，来实现让OS帮忙回收子进程资源，linux特供)
                // // 虽然进程对SIGCHLD的默认行为也是忽略，但默认的忽略和手动设置的忽略SIG_IGN效果不一样(目前仅限SIGCHLD， 因为我只知道这一个)
                // pid_t pid = fork();
                // if (pid == 0) { // child
                //     close(_listen_sockfd);
                //     ioService(sockfd);
                //     exit(EXIT_SUCCESS);
                // }

                // // 父进程不需要做任何事情，直接继续循环监听客户端请求
                // // 当子进程退出时，OS会回收资源

                // //* ver3 多线程 多线程比多进程的开销更小
                // pthread_t tid;
                // ThreadData* td = new ThreadData(this, sockfd);
                // pthread_create(&tid, nullptr, threadRoutine, (void*)td);

                //* ver4 线程池 尽管创建线程的开销较小，但仍有开销
                //* 线程池避免了频繁创建新线程
                // 向线程池中投放任务
                ThreadPool<Task>::getSingleton()->push(Task(sockfd, ioService));

            }//@end for(;;)
        }//@end void start()

    private:
        // static是为了消除this指针
        // 但是没有this指针的话就不能访问非static成员了，所以要借助上下文传入一些信息
        static void* threadRoutine(void* args) {
            // 线程detach自己本身，让自己终止时不需要其他线程join来回收资源，而是让OS来回收，
            // 在让OS回收资源这方面，多线程比多进程更方便
            pthread_detach(pthread_self()); 

            ThreadData* td = static_cast<ThreadData*>(args);
            ioService(td->_socket);

            delete td;
            return nullptr;
        }

    }; //@end class TcpServer
} //@end namespace server